/***********************************************************************
 * mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
 *  
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ***********************************************************************/
package helloWorld;

import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.nio.DoubleBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.media.opengl.GL;

import org.mt4j.MTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.visibleComponents.font.FontManager;
import org.mt4j.components.visibleComponents.shapes.MTEllipse;
import org.mt4j.components.visibleComponents.widgets.MTTextArea;
import org.mt4j.components.visibleComponents.widgets.progressBar.MTProgressBar;
import org.mt4j.input.IMTInputEventListener;
import org.mt4j.input.inputData.InputCursor;
import org.mt4j.input.inputData.MTInputEvent;
import org.mt4j.input.inputProcessors.IGestureEventListener;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleEvent;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor.TapAndHoldEvent;
import org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor.TapAndHoldProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
import org.mt4j.input.inputProcessors.globalProcessors.rawFingerProcessor.RawFingerEvent;
import org.mt4j.input.inputProcessors.globalProcessors.rawFingerProcessor.RawFingerProcessor;
import org.mt4j.sceneManagement.AbstractScene;
import org.mt4j.sceneManagement.IPreDrawAction;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.MTColor;
import org.mt4j.util.camera.MTCamera;
import org.mt4j.util.math.Matrix;
import org.mt4j.util.math.Vector3D;

import processing.opengl.PGraphicsOpenGL;

import com.modestmaps.TestInteractiveMap;
import com.modestmaps.geo.Location;
import com.modestmaps.providers.AbstractMapProvider;
import com.modestmaps.providers.Microsoft;

public class MapsScene extends AbstractScene implements MouseWheelListener,
		MouseListener, IMTInputEventListener {
	private TestInteractiveMap map;
	private MTApplication p;
	private MTComponent fingerCircleContainer;
	private MTComponent fotoTagContainer;
	private MTComponent fotoContainer;
	private MTComponent buttonContainer;
	private HashMap<InputCursor, MTEllipse> motionIDToFingerPoint;
	private MTCamera defaultCenterCam;
	private MTProgressBar progressBar;
	private ArrayList<Location> locations = new ArrayList<Location>();
	private ArrayList<String> captions = new ArrayList<String>();
	private int numberOfLocations;
	private int currentLocation = 0;
	private MTTextArea textArea;
	private MTEllipse marker;

	public MapsScene(MTApplication mtApplication, String name) {
		super(mtApplication, name);
		this.p = mtApplication;
		this.getCanvas().setFrustumCulling(true);
		if (!MT4jSettings.getInstance().isOpenGlMode()) {
			System.err
					.println("Scene only usable when using the OpenGL renderer! - See settings.txt");
			return;
		}
		/*locations.add(new Location(50.872666f, 4.698232f));
		locations.add(new Location(50.867686f, 4.678526f));
		locations.add(new Location(50.877853f, 4.699241f));
		captions.add("Kot van Frans");
		captions.add("Campus Heverlee");
		captions.add("Oude Markt");*/
		List<DBLocation> dbLocs = DBInterface.getInstance().getLocations();
		for(int i = 0; i < dbLocs.size(); ++i){
			Location newLoc = new Location((float)dbLocs.get(i).latitude, (float)dbLocs.get(i).longtitude);
			locations.add(newLoc);
			String newCaption = "Store->" + dbLocs.get(i).store + "\n" +
								"Artist->" + dbLocs.get(i).artist + "\n" +
								"Album->" + dbLocs.get(i).album + "\n";
			captions.add(newCaption);
		}
		this.numberOfLocations = dbLocs.size();

		textArea = new MTTextArea(mtApplication, FontManager.getInstance()
				.createFont(mtApplication, "arial.ttf", 25, // Font size
						new MTColor(0, 0, 0), // Font fill color
						new MTColor(0, 0, 0))); // Font outline
		// colortextArea.setNoFill(true);
		textArea.setNoStroke(true);
		this.getCanvas().addChild(textArea);
		textArea.setPositionGlobal(new Vector3D(mtApplication.width / 2f,50));
		
		marker = new MTEllipse(mtApplication, new Vector3D(mtApplication.width / 2f, mtApplication.height / 2f), 5, 5);
		marker.setFillColor(new MTColor(255,0,0));
		this.getCanvas().addChild(marker);

		defaultCenterCam = new MTCamera(p);

		// Container for the foto tags on the map
		fotoTagContainer = new MTComponent(p);

		// Container for the touch points
		fingerCircleContainer = new MTComponent(p);
		fingerCircleContainer.attachCamera(defaultCenterCam);
		fingerCircleContainer.setDepthBufferDisabled(true);

		// Container for the Fotos
		fotoContainer = new MTComponent(p);
		fotoContainer.attachCamera(defaultCenterCam);

		// Container for the buttons
		buttonContainer = new MTComponent(p);
		buttonContainer.attachCamera(defaultCenterCam);

		// Map to match the finger to the visible touch point circle
		motionIDToFingerPoint = new HashMap<InputCursor, MTEllipse>();

		RawFingerProcessor allInputAnalyzer = new RawFingerProcessor();
		this.registerGlobalInputProcessor(allInputAnalyzer);
		allInputAnalyzer.addProcessorListener(this);

		// Create map
		AbstractMapProvider mapProvider = new Microsoft.RoadProvider();
		map = new TestInteractiveMap(mtApplication, mapProvider);

		map.setName("map");
		map.MAX_IMAGES_TO_KEEP = 256;

		map.registerInputProcessor(new DragProcessor(mtApplication));
		map.setGestureAllowance(DragProcessor.class, true);
		map.addGestureListener(DragProcessor.class, new MapDrag());

		map.registerInputProcessor(new ScaleProcessor(mtApplication));
		map.setGestureAllowance(ScaleProcessor.class, true);
		map.addGestureListener(ScaleProcessor.class, new MapScale());

		map.registerInputProcessor(new TapAndHoldProcessor(mtApplication));
		map.setGestureAllowance(TapAndHoldProcessor.class, true);
		map.addGestureListener(TapAndHoldProcessor.class, new NextLocation());

		map.setGestureAllowance(DragProcessor.class, true);
		map.setGestureAllowance(RotateProcessor.class, false);
		map.setGestureAllowance(ScaleProcessor.class, true);
		map.setGestureAllowance(TapProcessor.class, false);
		map.setGestureAllowance(TapAndHoldProcessor.class, true);
		this.getCanvas().addChild(map);

		// Set up the progressbar
		progressBar = new MTProgressBar(p, p.loadFont(MT4jSettings
				.getInstance().getDefaultFontPath()
				+ "Ziggurat.vlw"));
		progressBar.attachCamera(defaultCenterCam);
		progressBar.setDepthBufferDisabled(true);
		progressBar.setVisible(false);
		progressBar.setPickable(false);
		this.getCanvas().addChild(progressBar);

		this.getCanvas().addChild(fotoTagContainer);
		this.getCanvas().addChild(fotoContainer);
		this.getCanvas().addChild(buttonContainer);
		this.getCanvas().addChild(fingerCircleContainer);

		map.sc = 4;
		this.updateTagContainerScale();
		map.panAndZoomIn(locations.get(0));
	}

	/**
	 * @author Chris
	 */
	private class MapDrag implements IGestureEventListener {
		public boolean processGestureEvent(MTGestureEvent g) {
			if (g instanceof DragEvent) {
				DragEvent dragEvent = (DragEvent) g;
				Vector3D tVect = dragEvent.getTranslationVect();
				map.move(tVect.x, tVect.y);
				updateTagContainerScale();
			}
			return true;
		}
	}

	/**
	 * @author Besitzer
	 */
	private class MapScale implements IGestureEventListener {
		public boolean processGestureEvent(MTGestureEvent g) {
			if (g instanceof ScaleEvent) {
				ScaleEvent se = (ScaleEvent) g;

				float x = se.getScaleFactorX();
				float y = se.getScaleFactorY();

				if (x != 1 && y != 1) {
					map.sc *= x;

					updateTagContainerScale();

					MTComponent[] tags = fotoTagContainer.getChildren();
					for (int i = 0; i < tags.length; i++) {
						MTComponent baseComponent = tags[i];
						if (baseComponent instanceof MTEllipse) {
							MTEllipse ell = (MTEllipse) baseComponent;
							float scX = 1f / x;
							float scY = 1f / y;
							ell.scale(scX, scY, 1, ell
									.getCenterPointRelativeToParent(),
									TransformSpace.RELATIVE_TO_PARENT);
						}
					}
				}
			}
			return true;
		}
	}

	private class NextLocation implements IGestureEventListener {
		public boolean processGestureEvent(MTGestureEvent g) {
			if (g instanceof TapAndHoldEvent) {
				map.setZoom(17);
				currentLocation += 1;
				Location location = locations.get(currentLocation
						% numberOfLocations);
				map.panTo(location);
				textArea.setText(captions.get(currentLocation
						% numberOfLocations));
				marker.sendToFront();
				textArea.sendToFront();
			}
			return true;
		}
	}

	// @Override
	public void init() {
		p.registerKeyEvent(this);
		p.addMouseWheelListener(this);
		p.addMouseListener(this);
	}

	// @Override
	public void shutDown() {
		p.unregisterKeyEvent(this);
		p.removeMouseWheelListener(this);
		p.removeMouseListener(this);
	}

	private DoubleBuffer model = DoubleBuffer.allocate(16);
	private Matrix mgl = new Matrix();

	/**
	 * Kind of a hack to fit the scale of the foto tags to the map scale. Has to
	 * be called each time the map scale changes.
	 */
	private void updateTagContainerScale() {
		model.clear();
		PGraphicsOpenGL pgl = ((PGraphicsOpenGL) p.g);
		GL gl = pgl.gl;

		gl.glPushMatrix();
		gl.glScalef(1, -1, 1);
		gl.glTranslatef(p.width / 2, p.height / 2, 0);
		gl.glScalef((float) map.sc, (float) map.sc, 1);
		gl.glTranslatef((float) map.tx, (float) map.ty, 0);
		gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model);
		gl.glPopMatrix();

		try {
			mgl.set(new float[] { (float) model.get(0), (float) model.get(4),
					(float) model.get(8), (float) model.get(12),
					(float) model.get(1), (float) model.get(5),
					(float) model.get(9), (float) model.get(13),
					(float) model.get(2), (float) model.get(6),
					(float) model.get(10), (float) model.get(14),
					(float) model.get(3), (float) model.get(7),
					(float) model.get(11), (float) model.get(15) });
		} catch (Exception e) {
			e.printStackTrace();
		}
		fotoTagContainer.setLocalMatrix(mgl);
	}

	public TestInteractiveMap getMap() {
		return map;
	}

	public void setMap(TestInteractiveMap map) {
		this.map = map;
	}

	// @Override
	public boolean processInputEvent(MTInputEvent inEvt) {
		if (inEvt instanceof RawFingerEvent) {
			/* draw blobs where fingers touch */
			RawFingerEvent ie = (RawFingerEvent) inEvt;

			switch (ie.getId()) {
			case MTGestureEvent.GESTURE_DETECTED:
				MTEllipse fingerCircle = new MTEllipse(p,
						ie.getInputPosition(), 6, 6, 10);
				fingerCircle.setPickable(false);
				fingerCircle.setNoFill(true);
				fingerCircle.setDrawSmooth(true);
				fingerCircle.setStrokeWeight(2);
				fingerCircle.setStrokeColor(new MTColor(40, 130, 220, 255));
				motionIDToFingerPoint.put(ie.getInputMotion(), fingerCircle);
				fingerCircleContainer.addChild(fingerCircle);
				break;
			case MTGestureEvent.GESTURE_UPDATED:
				MTEllipse circle = motionIDToFingerPoint.get(ie
						.getInputMotion());
				circle.setPositionGlobal(ie.getInputPosition());
				break;
			case MTGestureEvent.GESTURE_ENDED:
				MTEllipse e = motionIDToFingerPoint.get(ie.getInputMotion());
				fingerCircleContainer.removeChild(e);
				e = null;
				break;
			default:
				break;
			}
		}
		return true;
	}

	public void mouseWheelMoved(MouseWheelEvent e) {
		int notches = e.getWheelRotation();
		System.out.println(notches);
		if (notches < 0) {
			// map.zoomIn();
			// map.zoom(0.5f);
			map.sc *= 1.1f;
			p.getCurrentScene().registerPreDrawAction(new IPreDrawAction() {
				public boolean isLoop() {
					return false;
				}

				public void processAction() {
					updateTagContainerScale();
				}
			});
		} else {
			// map.zoomOut();
			map.sc *= 0.9f;
			p.getCurrentScene().registerPreDrawAction(new IPreDrawAction() {
				public boolean isLoop() {
					return false;
				}

				public void processAction() {
					updateTagContainerScale();
				}
			});
		}
	}

	// @Override
	public void mouseClicked(MouseEvent arg0) {
		int b = arg0.getButton();
		switch (b) {
		case MouseEvent.BUTTON1:
			map.setZoom(17);
			currentLocation += 1;
			Location location = locations.get(this.currentLocation
					% this.numberOfLocations);
			map.panTo(location);
			textArea.setText(captions.get(currentLocation % numberOfLocations));
			textArea.sendToFront();
			marker.sendToFront();
			break;
		default:
			break;
		}
	}

	// @Override
	public void mouseEntered(MouseEvent arg0) {
	}

	// @Override
	public void mouseExited(MouseEvent arg0) {
	}

	// @Override
	public void mousePressed(MouseEvent arg0) {
	}

	// @Override
	public void mouseReleased(MouseEvent arg0) {
	}

	/**
	 * 
	 * @param e
	 */
	public void keyEvent(KeyEvent e) {
		int evtID = e.getID();
		if (evtID != KeyEvent.KEY_PRESSED)
			return;

		switch (e.getKeyCode()) {
		case KeyEvent.VK_PLUS:
			map.zoomIn();
			this.updateTagContainerScale();
			break;
		case KeyEvent.VK_MINUS:
			map.zoomOut();
			this.updateTagContainerScale();
			break;
		case KeyEvent.VK_F9:
			Location lisbon = new Location(38.693f, -9.198f);
			map.setCenterZoom(lisbon, 15);
			System.out.println("Center set to location: " + lisbon);
			this.updateTagContainerScale();
			break;
		default:
			break;
		}
	}
}
